﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.Data;
using System.IO;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;

using Toenda.Foundation;
using Toenda.Foundation.IO;

namespace Toenda.Foundation.Data {
	/// <summary>
	/// Class RestoreItem
	/// </summary>
	public class RestoreItem {
		private string _name;
		private string _filename;
		private DateTime _date;
		private DataTable _dt_header;
		private DataTable _dt_filelist;
		private DataTable _dt_media;

		/// <summary>
		/// Default Ctor
		/// </summary>
		public RestoreItem() {
		}

		/// <summary>
		/// Specific Ctor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="filename"></param>
		/// <param name="date"></param>
		public RestoreItem(
			string name,
			string filename,
			DateTime date) {
			this._name = name;
			this._filename = filename;
			this._date = date;
		}

		/// <summary>
		/// Specific Ctor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="filename"></param>
		/// <param name="date"></param>
		/// <param name="header"></param>
		/// <param name="filelist"></param>
		/// <param name="media"></param>
		public RestoreItem(
			string name,
			string filename,
			DateTime date,
			DataTable header,
			DataTable filelist,
			DataTable media) {
			this._name = name;
			this._filename = filename;
			this._date = date;
			this._dt_header = header;
			this._dt_filelist = filelist;
			this._dt_media = media;
		}

		/// <summary>
		/// Get or set the name
		/// </summary>
		public string Name {
			get { return this._name; }
			set { this._name = value; }
		}

		/// <summary>
		/// Get or set the filename
		/// </summary>
		public string Filename {
			get { return this._filename; }
			set { this._filename = value; }
		}

		/// <summary>
		/// Get or set the date
		/// </summary>
		public DateTime Date {
			get { return this._date; }
			set { this._date = value; }
		}

		/// <summary>
		/// Get or set the header
		/// </summary>
		public DataTable Header {
			get { return this._dt_header; }
			set { this._dt_header = value; }
		}

		/// <summary>
		/// Get or set the filelist
		/// </summary>
		public DataTable Filelist {
			get { return this._dt_filelist; }
			set { this._dt_filelist = value; }
		}

		/// <summary>
		/// Get or set the media
		/// </summary>
		public DataTable Media {
			get { return this._dt_media; }
			set { this._dt_media = value; }
		}
	}

	/// <summary>
	/// Class DBBackup
	/// </summary>
	public class BackupRestoreHandler {
		private DALSettings _dbcfg;
		private string _save_path;

		public event BackupPercentEventHandler BackupPercentEvent;
		public delegate void BackupPercentEventHandler(int percentValue);

		public event RestorePercentEventHandler RestorePercentEvent;
		public delegate void RestorePercentEventHandler(int percentValue);

		public event BackupCompleteEventHandler BackupCompleteEvent;
		public delegate void BackupCompleteEventHandler(SqlError error);

		public event RestoreCompleteEventHandler RestoreCompleteEvent;
		public delegate void RestoreCompleteEventHandler(SqlError error);

		// ---------------------------------------------------
		// CONSTRUCTORS
		// ---------------------------------------------------

		/// <summary>
		/// Default Ctor
		/// </summary>
		/// <param name="config">The DAL configuration object</param>
		public BackupRestoreHandler(DALSettings config) {
			this._dbcfg = config;
		}

		/// <summary>
		/// Default Ctor
		/// </summary>
		/// <param name="connectionString">The connection string</param>
		public BackupRestoreHandler(string connectionString) {
			if(connectionString.Trim() != "") {
				this._dbcfg = UdlParser.ParseConnectionString(connectionString);
			}
		}

		/// <summary>
		/// Default Ctor
		/// </summary>
		/// <param name="config">The DAL configuration object</param>
		/// <param name="savePath">The path where the backups/restores located</param>
		public BackupRestoreHandler(DALSettings config, string savePath) {
			this._dbcfg = config;
			this._save_path = savePath;
		}

		/// <summary>
		/// Default Ctor
		/// </summary>
		/// <param name="connectionString">The connection string</param>
		/// <param name="savePath">The path where the backups/restores located</param>
		public BackupRestoreHandler(string connectionString, string savePath) {
			if(connectionString.Trim() != "") {
				this._dbcfg = UdlParser.ParseConnectionString(connectionString);
			}

			if(savePath.Trim() != "") {
				this._save_path = savePath;
			}
		}

		// ---------------------------------------------------
		// PROPERTIES
		// ---------------------------------------------------

		/// <summary>
		/// Get or set the save path
		/// </summary>
		public string SavePath {
			get { return this._save_path; }
			set { this._save_path = value; }
		}

		// ---------------------------------------------------
		// EVENTS
		// ---------------------------------------------------

		/// <summary>
		/// Event backup_PercentComplete
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void backup_PercentComplete(object sender, PercentCompleteEventArgs e) {
			this.BackupPercentEvent(e.Percent);
		}

		/// <summary>
		/// Event restore_PercentComplete
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void restore_PercentComplete(object sender, PercentCompleteEventArgs e) {
			this.RestorePercentEvent(e.Percent);
		}

		/// <summary>
		/// Event backup_Complete
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void backup_Complete(object sender, ServerMessageEventArgs e) {
			this.BackupCompleteEvent(e.Error);
		}

		/// <summary>
		/// Event restore_Complete
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void restore_Complete(object sender, ServerMessageEventArgs e) {
			this.RestoreCompleteEvent(e.Error);
		}

		// ---------------------------------------------------
		// PUBLIC MEMBERS
		// ---------------------------------------------------

		/// <summary>
		/// Get the name of the database from the filename of a backup file
		/// </summary>
		/// <param name="filename"></param>
		/// <returns></returns>
		public string GetBackupNameFromBackupFilename(string filename) {
			return filename.Substring(0, filename.IndexOf("_"));
		}

		/// <summary>
		/// Get a datetime from a backup filename
		/// </summary>
		/// <param name="filename"></param>
		/// <returns></returns>
		public DateTime GetDateTimeFromBackupFilename(string filename) {
			//         exoCRM_23092007030736.bak
			// MovieMaticTest_20090227193305

			int start = filename.IndexOf("_") + 1;
			int year = DateTime.Now.Year;
			int month = DateTime.Now.Month;
			int day = DateTime.Now.Day;
			int hour = DateTime.Now.Hour;
			int minute = DateTime.Now.Minute;
			int second = DateTime.Now.Second;

			if(int.TryParse(filename.Substring(start, 4), out year)) {
				if(int.TryParse(filename.Substring(start + 4, 2), out month)) {
					if(int.TryParse(filename.Substring(start + 6, 2), out day)) {
						if(int.TryParse(filename.Substring(start + 8, 2), out hour)) {
							if(int.TryParse(filename.Substring(start + 10, 2), out minute)) {
								if(int.TryParse(filename.Substring(start + 12, 2), out second)) {
									return new DateTime(year, month, day, hour, minute, second);
								}
							}
						}
					}
				}
			}

			return new DateTime(1, 1, 1, 1, 1, 1);
		}

		/// <summary>
		/// Get a list of restores
		/// </summary>
		/// <returns></returns>
		public DateTime GetLastBackupDate() {
			DateTime dt;

			ServerConnection cn = new ServerConnection(
				this._dbcfg.Server,
				this._dbcfg.User,
				this._dbcfg.Password
			);

			//if(this._dbcfg.TrustedConnectionValue) {
			//    cn.LoginSecure = true;
			//}
			//else {
			//    cn.LoginSecure = false;
			//}

			Server svr = new Server(cn);

			try {
				svr.ConnectionContext.Connect();

				dt = svr.Databases[this._dbcfg.Database].LastBackupDate;
			}
			catch(ConnectionFailureException cnex) {
				throw new ConnectionFailureException(
					"Error on connect the server [MSG:" + cnex.Message + "].",
					cnex.InnerException
				);
			}
			finally {
				try {
					svr.ConnectionContext.Disconnect();
				}
				catch(SmoException smoex) {
					throw new SmoException(
						"Error on disconnect from server [MSG:" + smoex.Message + "].",
						smoex.InnerException
					);
				}
			}

			return dt;
		}

		/// <summary>
		/// Get a list of restores
		/// </summary>
		/// <returns></returns>
		/// <exception cref="ArgumentNullException">If the properties SavePath is not set.</exception>
		public List<RestoreItem> GetRestoreItemList() {
			if(this._save_path == null || this._save_path.Trim() == "") {
				throw new ArgumentNullException(
					"The SavePath properties is not set!",
					new Exception("Internal ArgumentNullException")
				);
			}

			return this.GetRestoreItemList(this._save_path);
		}

		/// <summary>
		/// Get a list of restores
		/// </summary>
		/// <returns></returns>
		public List<RestoreItem> GetRestoreItemList(string path) {
			List<RestoreItem> list = new List<RestoreItem>();

			ServerConnection cn = new ServerConnection(
				this._dbcfg.Server,
				this._dbcfg.User,
				this._dbcfg.Password
			);

			//if(this._dbcfg.TrustedConnectionValue) {
			//    cn = true;
			//}
			//else {
			//    cn.LoginSecure = false;
			//}

			Server svr = new Server(cn);

			try {
				svr.ConnectionContext.Connect();
				
				DirectoryInfo dir = new DirectoryInfo(this.SavePath);
				FileInfo[] files = dir.GetFiles("*.bak");

				foreach(FileInfo fi in files) {
					//Restore r = new Restore();

					//r.Devices.Add(
					//    new BackupDeviceItem(
					//        fi.FullName,
					//        DeviceType.File
					//    )
					//);

					//r.ReadBackupHeader(svr),
					//r.ReadFileList(svr),
					//r.ReadMediaHeader(svr)

					list.Add(
						new RestoreItem(
							this.GetBackupNameFromBackupFilename(fi.Name),
							fi.FullName,
							this.GetDateTimeFromBackupFilename(fi.Name)
						)
					);
				}
			}
			catch(ConnectionFailureException cnex) {
				throw new ConnectionFailureException(
					"Error on connect the server [MSG:" + cnex.Message + "].",
					cnex.InnerException
				);
			}
			finally {
				try {
					svr.ConnectionContext.Disconnect();
				}
				catch(SmoException smoex) {
					throw new SmoException(
						"Error on disconnect from server [MSG:" + smoex.Message + "].",
						smoex.InnerException
					);
				}
			}

			return list;
		}

		/// <summary>
		/// Create a database backup
		/// </summary>
		/// <exception cref="ArgumentNullException">If the properties SavePath is not set.</exception>
		public string BackupDatabase() {
			if(this._save_path == null || this._save_path.Trim() == "") {
				throw new ArgumentNullException(
					"The SavePath properties is not set!",
					new Exception("Internal ArgumentNullException")
				);
			}

			string sourceFileName = "";

			ServerConnection cn_from = new ServerConnection(
				this._dbcfg.Server,
				this._dbcfg.User,
				this._dbcfg.Password
			);

			//if(this._dbcfg.TrustedConnectionValue) {
			//    cn_from.LoginSecure = true;
			//}
			//else {
			//    cn_from.LoginSecure = false;
			//}

			Server svr = new Server(cn_from);

			try {
				bool differential = false;

				// connect
				svr.ConnectionContext.Connect();

				//create backup
				Database src_db = svr.Databases[this._dbcfg.Database];

				if(src_db.Status == DatabaseStatus.Normal) {
					src_db = null;

					sourceFileName = this._dbcfg.Database
						+ "_"
						+ DateTime.Now.ToFileNameString()
						+ ".bak";

					Backup backup = new Backup();
					backup.Complete += new ServerMessageEventHandler(backup_Complete);
					backup.PercentComplete += new PercentCompleteEventHandler(backup_PercentComplete);
					backup.Action = BackupActionType.Database;
					backup.Incremental = differential;
					backup.Database = this._dbcfg.Database;

					backup.Devices.Add(
						new BackupDeviceItem(
							this._save_path + "\\" + sourceFileName,
							DeviceType.File
						)
					);

					try {
						backup.SqlBackup(svr);
						backup.Wait();
					}
					catch(Exception ex) {
						throw ex;
					}
				}
			}
			catch(ConnectionFailureException cnex) {
				throw new ConnectionFailureException(
					"Error on connect the server [MSG:" + cnex.Message + "].",
					cnex.InnerException
				);
			}
			finally {
				try {
					svr.ConnectionContext.Disconnect();
				}
				catch(SmoException smoex) {
					throw new SmoException(
						"Error on disconnect from server [MSG:" + smoex.Message + "].",
						smoex.InnerException
					);
				}
			}

			return sourceFileName;
		}

		/// <summary>
		/// Restore a database
		/// </summary>
		/// <param name="sourceFilename"></param>
		/// <param name="deleteBeforeRestore"></param>
		/// <returns></returns>
		public bool RestoreDatabase(string sourceFilename, bool deleteBeforeRestore) {
			return this.RestoreDatabase(sourceFilename, deleteBeforeRestore, true, false);
		}

		/// <summary>
		/// Restore a database
		/// </summary>
		/// <param name="sourceFilename"></param>
		/// <param name="deleteBeforeRestore"></param>
		/// <returns></returns>
		public bool RestoreDatabase(
			string sourceFilename,
			bool deleteBeforeRestore,
			bool replaceDatabase,
			bool partialRestore) {
			bool returnResult = true;

			if(!FileSystem.Self.IsPathIsFilename(sourceFilename)) {
				sourceFilename = this._save_path + "\\" + sourceFilename;
			}

			ServerConnection cn_from = new ServerConnection(
				this._dbcfg.Server,
				this._dbcfg.User,
				this._dbcfg.Password
			);

			//if(this._dbcfg.TrustedConnectionValue) {
			//    cn_from.LoginSecure = true;
			//}
			//else {
			//    cn_from.LoginSecure = false;
			//}

			Server svr = new Server(cn_from);

			try {
				// connect
				svr.ConnectionContext.Connect();

				if(deleteBeforeRestore) {
					svr.KillDatabase(this._dbcfg.Database);
				}

				Restore restore = new Restore();
				restore.Complete += new ServerMessageEventHandler(restore_Complete);
				restore.PercentComplete += new PercentCompleteEventHandler(restore_PercentComplete);
				restore.Action = RestoreActionType.Database;
				restore.ReplaceDatabase = replaceDatabase;
				restore.Partial = partialRestore;
				restore.Database = this._dbcfg.Database;

				restore.Devices.Add(
					new BackupDeviceItem(
						sourceFilename,
						DeviceType.File
					)
				);

				try {
					restore.SqlRestore(svr);
					restore.Wait();
				}
				catch(Exception ex) {
					returnResult = false;
					throw ex;
				}
			}
			catch(ConnectionFailureException cnex) {
				returnResult = false;

				throw new ConnectionFailureException(
					"Error on connect the server [MSG:" + cnex.Message + "].",
					cnex.InnerException
				);
			}
			finally {
				try {
					svr.ConnectionContext.Disconnect();
				}
				catch(SmoException smoex) {
					returnResult = false;

					throw new SmoException(
						"Error on disconnect from server [MSG:" + smoex.Message + "].",
						smoex.InnerException
					);
				}
			}

			return returnResult;
		}
	}
}
